#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Compute function coverage from coverage.py JSON export.

Usage:
  1) Run with branch data enabled (via .coveragerc or 'python -m coverage run --branch ...'):
       python -m coverage run your_script.py [args...]
  2) Export JSON:
       python -m coverage json -o coverage_proposed.json
  3) Compute function coverage:
       python function_coverage_from_coverage_json.py coverage_proposed.json [path_prefix ...]

If no path prefixes are given, all files present in coverage_proposed.json are processed.
Outputs:
  - Summary table to stdout
  - CSV written next to coverage_proposed.json as function_coverage_1.csv
"""
import sys, json, ast
from pathlib import Path
import csv

def load_cov_json(p: Path):
    data = json.loads(p.read_text(encoding='utf-8'))
    files = data.get('files') or {}
    return files

def add_parents(tree):
    for parent in ast.walk(tree):
        for child in ast.iter_child_nodes(parent):
            child.parent = parent

def list_functions_qualified(py_path: Path):
    text = py_path.read_text(encoding='utf-8', errors='ignore')
    tree = ast.parse(text, filename=str(py_path))
    add_parents(tree)
    funs = []
    def qualname(node):
        parts = [node.name]
        p = getattr(node, 'parent', None)
        while p is not None:
            if isinstance(p, ast.ClassDef):
                parts.append(p.name)
            p = getattr(p, 'parent', None)
        parts.reverse()
        return '.'.join(parts)
    for node in ast.walk(tree):
        if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
            start = getattr(node, 'lineno', None)
            end = getattr(node, 'end_lineno', None)
            if start is None:
                continue
            if end is None:
                end = max((getattr(n, 'lineno', start) for n in ast.walk(node)), default=start)
            funs.append((qualname(node), start, end))
    return funs

def main(argv):
    if len(argv) < 2:
        print('Usage: python function_coverage_from_coverage_json.py coverage_proposed.json [path_prefix ...]')
        return 2
    cov_path = Path(argv[1]).resolve()
    prefixes = [str(Path(p).resolve()) for p in argv[2:]] if len(argv) > 2 else []
    files = load_cov_json(cov_path)

    rows = []
    for fname, meta in files.items():
        fpath = str(Path(fname).resolve())
        if prefixes and not any(fpath.startswith(px) for px in prefixes):
            continue
        executed = set(meta.get('executed_lines') or [])
        try:
            funs = list_functions_qualified(Path(fpath))
        except Exception:
            continue
        total = len(funs)
        covered = 0
        details = []
        for q, start, end in funs:
            fun_lines = set(range(start, end+1))
            hit = bool(fun_lines & executed)
            covered += 1 if hit else 0
            details.append((q, start, end, hit))
        cov_pct = (covered / total * 100.0) if total else 100.0
        rows.append((fpath, total, covered, round(cov_pct, 2), details))

    print('Function Coverage Summary\n==========================')
    total_funcs = sum(r[1] for r in rows)
    total_cov   = sum(r[2] for r in rows)
    overall = (total_cov/total_funcs*100.0) if total_funcs else 100.0
    print(f'Overall: {total_cov}/{total_funcs} = {overall:.2f}%')
    for path, tot, cov, pct, _ in rows:
        print(f'- {path}: {cov}/{tot} = {pct:.2f}%')

    out_csv = cov_path.parent / 'function_coverage_1.csv'
    with out_csv.open('w', newline='', encoding='utf-8') as fh:
        wr = csv.writer(fh)
        wr.writerow(['file','function','start_line','end_line','covered'])
        for path, _, _, _, details in rows:
            for q, s, e, hit in details:
                wr.writerow([path, q, s, e, int(hit)])
    print(f'\nWrote CSV: {out_csv}')
    return 0

if __name__ == '__main__':
    raise SystemExit(main(sys.argv))
